package com.xiaoluyy.demo.config;


import com.xiaoluyy.demo.common.exception.ExceptionCode;
import com.xiaoluyy.common.log.LogFactory;
import com.xiaoluyy.common.log.ILog;
import com.xiaoluyy.common.log.util.LogIdThreadLocal;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.UUID;


@Aspect
@Component
public class ApiInterceptor {

    private static final ILog logger = LogFactory.getLog("api_logger");


    @Pointcut("execution(* com.xiaoluyy.demo.api.DemoService1Impl.*(..))")
    private void anyMethod(){
    }

    @Around("anyMethod()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        Object[] parames = pjp.getArgs();//获取目标方法体参数
        if(parames == null || parames.length < 1) {
            Object errorObject =  makeErrorObject(pjp, "error", -1, "参数要至少包含logId");
            logger.warn("参数要至少包含logId");
            return errorObject;
        }
        String logId = null;
        if(org.springframework.util.StringUtils.isEmpty(parames[0])) {//第一个参数必须为logId
            logId = UUID.randomUUID().toString();
        } else {
            logId = parames[0].toString();
        }
        LogIdThreadLocal.setLogId(logId);

        String className = pjp.getTarget().getClass().toString();//获取目标类名
        className = className.substring(className.indexOf("com"));
        String signature = pjp.getSignature().toString();//获取目标方法签名
        String methodName = signature.substring(signature.lastIndexOf(".") + 1, signature.indexOf("("));
        //访问进入日志
        logger.debug("starting call "+methodName+"()");
        logger.info("method:"+methodName+"(),params: " + Arrays.toString(parames));
        long startTime=System.currentTimeMillis();

        try{
            Object object = pjp.proceed();
            //访问退出日志
            appendLogIdToReturnObj(pjp, object, logId);
            logger.info("method:"+methodName+"(),response:"+ object+",took:" + (System.currentTimeMillis() - startTime));
            logger.debug(methodName + "() end.\n Took " + (System.currentTimeMillis() - startTime) + " ms");
            //从thread local 中清理logId
            LogIdThreadLocal.clean();
            return object;
        } catch (Exception e) {
            logger.info("call:"+methodName+"(),response exception,",e);
            Object errorObject =  makeErrorObject(pjp, logId, ExceptionCode.UnknownError.getCode(), e.getMessage());
            //从thread local 中清理logId
            LogIdThreadLocal.clean();
            return errorObject;
        }

    }

    /**
     * 创建错误返回对象
     * @param pjp
     * @param errorCode
     * @param errorMsg
     * @return
     * @throws Throwable
     */
    public Object makeErrorObject(ProceedingJoinPoint pjp, String logId, int errorCode, String errorMsg) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Class classType = method.getReturnType();
        Object errorObject=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
        Method setErrorCodeMethod=classType.getMethod("setErrorCode", new Class[]{int.class});
        setErrorCodeMethod.invoke(errorObject, new Object[]{errorCode});
        Method setErrorMsgMethod=classType.getMethod("setErrorMsg", new Class[]{String.class});
        setErrorMsgMethod.invoke(errorObject, new Object[]{errorMsg});
        Method setLogIdMethod=classType.getMethod("setLogId", new Class[]{String.class});
        setLogIdMethod.invoke(errorObject, new Object[]{logId});
        return errorObject;
    }

    public Object appendLogIdToReturnObj(ProceedingJoinPoint pjp, Object returnObj, String logId) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Class classType = method.getReturnType();
        Method setLogIdMethod=classType.getMethod("setLogId", new Class[]{String.class});
        setLogIdMethod.invoke(returnObj, new Object[]{logId});
        return returnObj;
    }
}